﻿/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;

namespace Bio.Views.Structure.Editor.Internal.Rendering.Primitives
{
    public class StrandArc
    {
        public StrandArc(int strandStartNucleotide, int strandEndNucleotide,
            Dictionary<int, Point> allLocations)
        {
            StrandStartNucleotideIndex = strandStartNucleotide;
            StrandEndNucleotideIndex = strandEndNucleotide;
            Debug.Assert(allLocations.ContainsKey(StrandStartNucleotideIndex) &&
                         allLocations.ContainsKey(StrandEndNucleotideIndex));
            AllLocations = allLocations;
            DesiredHeight = -1.0;
        }

        public void MoveNucleotideTo(int nucleotideIndex, Point location)
        {
            AllLocations[nucleotideIndex] = MoveNucleotideToInternal(nucleotideIndex, location);
            UpdateProposedArc();
        }

        public void MoveNucleotideBy(int nucleotideIndex, Point delta)
        {
            AllLocations[nucleotideIndex] = MoveNucleotideByInternal(nucleotideIndex, delta);
            UpdateProposedArc();
        }

        public void MoveBy(Point delta)
        {
            Dictionary<int, Point> newNtLocations = new Dictionary<int, Point>();
            foreach (var strandNt in AllLocations.Keys)
            {
                newNtLocations.Add(strandNt, MoveNucleotideByInternal(strandNt, delta));
            }
            AllLocations = newNtLocations;
            UpdateProposedArc();
        }

        public void ChangeArcHeight(double desiredHeight)
        {
            DesiredHeight = desiredHeight;
            UpdateProposedArc();
        }

        public Dictionary<int, Point> ChangeArcWidth(double desiredWidth)
        {
            if (desiredWidth <= 0)
                throw new ArgumentException("Error: Arc Width must be Positive");

            double factor = StrandArcDiameterVector.Length / desiredWidth;
            Vector newStrandArcDiameterVector = Vector.Divide(StrandArcDiameterVector, factor);
            Point currentArcEndStd = new Point(StrandEndNucleotideLocation.X + TranslationDelta.X, StrandEndNucleotideLocation.Y + TranslationDelta.Y);
            Point arcEndDelta = new Point(newStrandArcDiameterVector.X - currentArcEndStd.X, newStrandArcDiameterVector.Y - currentArcEndStd.Y);
            MoveNucleotideBy(StrandEndNucleotideIndex, arcEndDelta);
            Dictionary<int, Point> retValue = new Dictionary<int, Point>();
            retValue.Add(StrandEndNucleotideIndex, arcEndDelta);
            return retValue;
        }

        public Dictionary<int, Point> Snap()
        {
            Vector strandStartToOrigin = Vector.Divide(StrandArcDiameterVector, 2);
            Point currentOrigin = new Point(StrandStartNucleotideLocation.X + strandStartToOrigin.X, StrandStartNucleotideLocation.Y + strandStartToOrigin.Y);
            Dictionary<int, Point> mods = new Dictionary<int, Point>();
            //Now, since we know how many nucleotides have to be spaced out over 180 degrees, we can commence the snapping.

            if (StrandArcXRadius > 0 && StrandArcYRadius > 0 && AllLocations.Count > 2)
            {
                double angularIncrement = (Math.PI / (AllLocations.Count - 1));
                double currentAngularPosition = 0.0;
                foreach (var ntIdx in AllLocations.Keys)
                {
                    double xstd = StrandArcXRadius * Math.Cos(currentAngularPosition);
                    double ystd = StrandArcYRadius * Math.Sin(currentAngularPosition);
                    Point newDesiredLocation = new Point(currentOrigin.X - xstd, currentOrigin.Y - ystd);
                    Point deltaOverCurrent = new Point(newDesiredLocation.X - AllLocations[ntIdx].X, newDesiredLocation.Y - AllLocations[ntIdx].Y);
                    mods.Add(ntIdx, deltaOverCurrent);
                    MoveNucleotideByInternal(ntIdx, deltaOverCurrent);
                    currentAngularPosition += angularIncrement;
                }
            }
            UpdateProposedArc();
            return mods;
        }

        public Point StrandStartNucleotideLocation { get { return AllLocations[StrandStartNucleotideIndex]; } }
        public Point StrandEndNucleotideLocation { get { return AllLocations[StrandEndNucleotideIndex]; } }
        public Dictionary<int, Point> AllLocations { get; private set; }
        public Vector StrandArcDiameterVector { get; private set; }
        public double StrandArcXRadius { get; private set; }
        public double StrandArcYRadius { get; private set; }

        private int StrandStartNucleotideIndex { get; set; }
        private int StrandEndNucleotideIndex { get; set; }
        private Point TranslationDelta { get; set; }
        private double DesiredHeight { get; set; }

        private void UpdateProposedArc()
        {
            if (AllLocations.Count <= 1)
            {
                //A single nt loop
                StrandArcDiameterVector = new Vector();
                StrandArcXRadius = 0.0;
                StrandArcYRadius = 0.0;
                return;
            }

            Point strandStartStdCoord = new Point(StrandStartNucleotideLocation.X, -1 * StrandStartNucleotideLocation.Y);
            Point strandEndStdCoord = new Point(StrandEndNucleotideLocation.X, -1 * StrandEndNucleotideLocation.Y);
            TranslationDelta = new Point(0 - strandStartStdCoord.X, 0 - strandStartStdCoord.Y);

            Point t_strandStartStdCoord = new Point(strandStartStdCoord.X + TranslationDelta.X, strandStartStdCoord.Y + TranslationDelta.Y);
            Point t_strandEndStdCoord = new Point(strandEndStdCoord.X + TranslationDelta.X, strandEndStdCoord.Y + TranslationDelta.Y);

            StrandArcDiameterVector = new Vector(t_strandEndStdCoord.X, t_strandEndStdCoord.Y);
            StrandArcXRadius = (AllLocations.Count <= 2) ? 0.0 : StrandArcDiameterVector.Length / 2.0;
            if (DesiredHeight >= 0.0)
                StrandArcYRadius = DesiredHeight;
            else
                StrandArcYRadius = StrandArcXRadius;
        }

        private Point MoveNucleotideByInternal(int nucleotideIndex, Point delta)
        {
            if (!AllLocations.ContainsKey(nucleotideIndex))
                throw new ArgumentException(string.Format("Error: Nucleotide {0} is not within loop {1}", nucleotideIndex, StrandStartNucleotideIndex));
            return new Point(AllLocations[nucleotideIndex].X + delta.X, AllLocations[nucleotideIndex].Y + delta.Y);
        }

        private Point MoveNucleotideToInternal(int nucleotideIndex, Point location)
        {
            if (!AllLocations.ContainsKey(nucleotideIndex))
                throw new ArgumentException(string.Format("Error: Nucleotide {0} is not within loop {1}", nucleotideIndex, StrandStartNucleotideIndex));
            return location;
        }
    }
}
